﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SSTU.Contract.Facade;
using System.Data.Linq;
using System.Linq;
using System.Threading;
using SSTU.Contract.Common;
using System.Collections.Generic;
using SSTU.Model;
using SSTU.Management.Dependency;
using SSTU.Data;

namespace SSTU.Facade
{
	public class CacheStructureFacade : FacadeBase, IStructureFacade
	{
		private readonly IStructureFacade _innerStructureFacade;
		private readonly object GroupsSyncRoot = new object();

		/// <summary>
		/// Initializes a new instance of the <see cref="CacheStructureFacade"/> class.
		/// </summary>
		/// <param name="innerStructureFacade">The inner structure facade.</param>
		public CacheStructureFacade(IStructureFacade innerStructureFacade)
		{
			DependencyRequirement.Current.NotNullFromConstructorInjection<CacheStructureFacade, IStructureFacade>(innerStructureFacade);
			_innerStructureFacade = innerStructureFacade;
		}

		#region IStructureFacade

		/// <summary>
		/// Loads the faculties asynchronously and invoke callback.
		/// </summary>
		/// <param name="asyncCallback">The asynchronous callback.</param>
		public void LoadFacultiesAsync(FacadeAsyncCallback<IEnumerable<Faculty>> asyncCallback)
		{
			// HACK: From 1.0.0.2: cache is not using for Faculties.
			_innerStructureFacade.LoadFacultiesAsync(asyncCallback);
		}

		/// <summary>
		/// Loads the groups, by faculty identifier, asynchronously and invoke callback.
		/// </summary>
		/// <param name="facultyId">The faculty identifier.</param>
		/// <param name="asyncCallback">The asynchronous callback.</param>
		public void LoadGroupsAsync(string facultyId, FacadeAsyncCallback<IEnumerable<Group>> asyncCallback)
		{
			_innerStructureFacade.LoadGroupsAsync(facultyId, innerFacadeResponse =>
			{
				bool canProcess =
					innerFacadeResponse != null
					&& innerFacadeResponse.IsSuccess
					&& innerFacadeResponse.Data != null
					&& innerFacadeResponse.DataSource == FacadeResponseDataSource.Server;
				if (canProcess)
				{
					GroupEntity[] facultyGroups = null;
					lock (GroupsSyncRoot)
					{
						using (var dataContext = SstuDataContext.Create())
						{
							facultyGroups = dataContext.Groups.Where(groupEntity => groupEntity.FacultyId == facultyId).ToArray();
						}
					}
					foreach (var newGroup in innerFacadeResponse.Data)
					{
						var oldGroupEntity = facultyGroups.FirstOrDefault(groupEntity => groupEntity.Id == newGroup.Id);
						if (oldGroupEntity != null)
						{
							// Update 'IsFavorite' flag for correct loading in UI.
							newGroup.IsFavorite = oldGroupEntity.IsFavorite;
						}
					}
					ThreadPool.QueueUserWorkItem(o => { SaveGroupsInCache(facultyId, innerFacadeResponse.Data); });
				}
				asyncCallback(innerFacadeResponse);
			});
			DoAsync(FacadeResponseDataSource.Local, () => LoadGroupsFromCache(facultyId), asyncCallback);
		}

		/// <summary>
		/// Loads the group, by identifier, asynchronously and invoke callback.
		/// </summary>
		/// <param name="groupId">The group identifier.</param>
		/// <param name="asyncCallback">The asynchronous callback.</param>
		public void LoadGroupAsync(string groupId, FacadeAsyncCallback<Group> asyncCallback)
		{
			DoAsync(FacadeResponseDataSource.Local, () => LoadGroupFromCache(groupId), asyncCallback);
		}

		/// <summary>
		/// Adds the group, by identifier, to favorites asynchronously and invoke callback.
		/// </summary>
		/// <param name="groupId">The group identifier.</param>
		/// <param name="asyncCallback">The async callback.</param>
		public void AddGroupToFavoritesAsync(string groupId, FacadeAsyncCallback asyncCallback)
		{
			DoAsync(FacadeResponseDataSource.Local, () => SetGroupIsFavoriteInCache(groupId, true), asyncCallback);
		}

		/// <summary>
		/// Removes the group, by identifier, from favorites asynchronously and invoke callback.
		/// </summary>
		/// <param name="groupId">The group identifier.</param>
		/// <param name="asyncCallback">The async callback.</param>
		public void RemoveGroupFromFavoritesAsync(string groupId, FacadeAsyncCallback asyncCallback)
		{
			DoAsync(FacadeResponseDataSource.Local, () => SetGroupIsFavoriteInCache(groupId, false), asyncCallback);
		}

		/// <summary>
		/// Loads the favorite groups asynchronously and invoke callback.
		/// </summary>
		/// <param name="asyncCallback">The async callback.</param>
		public void LoadFavoriteGroupsAsync(FacadeAsyncCallback<IEnumerable<Group>> asyncCallback)
		{
			DoAsync(FacadeResponseDataSource.Local, () => LoadFavoriteGroupsFromCache(), asyncCallback);
		}

		#endregion // IStructureFacade

		#region Help Methods: Faculties

		/// <summary>
		/// Saves the faculties in cache.
		/// </summary>
		/// <param name="innerFacadeResponse">The inner facade response.</param>
		private void SaveFacultiesInCache(IEnumerable<Faculty> newFaculties)
		{
			lock (GroupsSyncRoot)
			{
				using (var dataContext = SstuDataContext.Create())
				{
					foreach (var newFaculty in newFaculties)
					{
						var oldFacultyEntity = dataContext.Faculties.FirstOrDefault(f => f.Id == newFaculty.Id);
						if (oldFacultyEntity == null)
						{
							var newFacultyEntity = new FacultyEntity(newFaculty.Id, newFaculty.ShortName, newFaculty.LongName);
							dataContext.Faculties.InsertOnSubmit(newFacultyEntity);
						}
						else
						{
							oldFacultyEntity.LongName = newFaculty.LongName;
							oldFacultyEntity.ShortName = newFaculty.ShortName;
						}
					}
					dataContext.SubmitChanges();
				}
			}
		}

		/// <summary>
		/// Loads the faculties from cache.
		/// </summary>
		/// <returns></returns>
		private IEnumerable<Faculty> LoadFacultiesFromCache()
		{
			IEnumerable<Faculty> faculties = null;
			lock (GroupsSyncRoot)
			{
				using (var dataContext = SstuDataContext.Create())
				{
					faculties = (
						from facultyEntity in dataContext.Faculties
						let faculty = new Faculty(facultyEntity.Id, facultyEntity.ShortName, facultyEntity.LongName)
						select faculty)
						.ToArray();
				}
			}
			return faculties;
		}

		#endregion // Help Methods: Faculties

		#region Help Methods: Groups

		/// <summary>
		/// Saves the groups in cache.
		/// </summary>
		/// <param name="facultyId">The faculty id.</param>
		/// <param name="newGroups">The new groups.</param>
		private void SaveGroupsInCache(string facultyId, IEnumerable<Group> newGroups)
		{
			lock (GroupsSyncRoot)
			{
				using (var dataContext = SstuDataContext.Create())
				{
					var oldGroups = dataContext.Groups.Where(entity => entity.FacultyId == facultyId).ToArray();
					var oldGroupsForDelete = oldGroups.Where(oldGroup => newGroups.FirstOrDefault(newGroup => newGroup.Id == oldGroup.Id) == null);
					dataContext.Groups.DeleteAllOnSubmit(oldGroupsForDelete);
					foreach (var newGroup in newGroups)
					{
						var oldGroupEntity = oldGroups.FirstOrDefault(entity => entity.Id == newGroup.Id);
						if (oldGroupEntity == null)
						{
							var newGroupEntity = new GroupEntity(newGroup.Id, newGroup.ShortName, newGroup.LongName, newGroup.IsFavorite, facultyId);
							dataContext.Groups.InsertOnSubmit(newGroupEntity);
						}
						else
						{
							oldGroupEntity.ShortName = newGroup.ShortName;
							oldGroupEntity.LongName = newGroup.LongName;
							oldGroupEntity.FacultyId = facultyId;
							// Update 'IsFavorite' flag for correct loading in UI.
							newGroup.IsFavorite = oldGroupEntity.IsFavorite;
						}
					}
					dataContext.SubmitChanges();
				}
			}
		}

		/// <summary>
		/// Loads the groups from cache.
		/// </summary>
		/// <param name="facultyId">The faculty id.</param>
		/// <returns></returns>
		private IEnumerable<Group> LoadGroupsFromCache(string facultyId)
		{
			IEnumerable<Group> groups = null;
			lock (GroupsSyncRoot)
			{
				using (var dataContext = SstuDataContext.Create())
				{
					var faculty = LoadFacultyFromDataContext(dataContext, facultyId);
					groups = (
						from groupEntity in dataContext.Groups
						where groupEntity.FacultyId == facultyId
						let @group = new Group(groupEntity.Id, groupEntity.ShortName, groupEntity.LongName, groupEntity.IsFavorite, faculty)
						select @group)
						.ToArray();
				}
			}
			return groups;
		}

		/// <summary>
		/// Loads the group from cache.
		/// </summary>
		/// <param name="groupId">The group id.</param>
		/// <returns></returns>
		private Group LoadGroupFromCache(string groupId)
		{
			Group group = null;
			lock (GroupsSyncRoot)
			{
				using (var dataContext = SstuDataContext.Create())
				{
					group = LoadGroupFromDataContext(dataContext, groupId);
				}
			}
			return group;
		}

		/// <summary>
		/// Loads the group from data context.
		/// </summary>
		/// <param name="dataContext">The dc.</param>
		/// <param name="groupId">The group id.</param>
		/// <returns></returns>
		private Group LoadGroupFromDataContext(SstuDataContext dataContext, string groupId)
		{
			Group group = null;
			var groupEntity = dataContext.Groups.FirstOrDefault(entity => entity.Id == groupId);
			if (groupEntity != null)
			{
				string facultyId = groupEntity.FacultyId;
				Faculty faculty = LoadFacultyFromDataContext(dataContext, facultyId);
				group = new Group(groupEntity.Id, groupEntity.ShortName, groupEntity.LongName, groupEntity.IsFavorite, faculty);
			}
			return group;
		}

		/// <summary>
		/// Loads the faculty from data context.
		/// </summary>
		/// <param name="dataContext">The data context.</param>
		/// <param name="facultyId">The faculty id.</param>
		/// <returns></returns>
		private Faculty LoadFacultyFromDataContext(SstuDataContext dataContext, string facultyId)
		{
			Faculty faculty = null;
			if (!string.IsNullOrEmpty(facultyId))
			{
				var facultyEntity = dataContext.Faculties.FirstOrDefault(entity => entity.Id == facultyId);
				if (facultyEntity != null)
				{
					faculty = new Faculty(facultyEntity.Id, facultyEntity.ShortName, facultyEntity.LongName);
				}
			}
			return faculty;
		}

		/// <summary>
		/// Sets the group favorite state in cache.
		/// </summary>
		/// <param name="groupId">The group id.</param>
		/// <param name="isFavorite">if set to <c>true</c> is favorite, <c>false</c> - not favorite.</param>
		private void SetGroupIsFavoriteInCache(string groupId, bool isFavorite)
		{
			lock (GroupsSyncRoot)
			{
				using (var dataContext = SstuDataContext.Create())
				{
					var group = dataContext.Groups.FirstOrDefault(g => g.Id == groupId);
					if (group != null)
					{
						group.IsFavorite = isFavorite;
					}
					dataContext.SubmitChanges();
				}
			}
		}

		/// <summary>
		/// Loads the favorite groups from cache.
		/// </summary>
		/// <returns></returns>
		private IEnumerable<Group> LoadFavoriteGroupsFromCache()
		{
			IEnumerable<Group> groups = null;
			lock (GroupsSyncRoot)
			{
				using (var dataContext = SstuDataContext.Create())
				{
					groups = (
						from groupEntity in dataContext.Groups
						where groupEntity.IsFavorite
						let facultyEntity = dataContext.Faculties.FirstOrDefault(f => f.Id == groupEntity.FacultyId)
						let faculty = facultyEntity == null ? null : new Faculty(facultyEntity.Id, facultyEntity.ShortName, facultyEntity.LongName)
						let @group = new Group(groupEntity.Id, groupEntity.ShortName, groupEntity.LongName, groupEntity.IsFavorite, faculty)
						select @group).ToArray();
				}
			}
			return groups;
		}

		#endregion // Help Methods: Groups
	}
}
